/*
 * TResizableFrame.cpp
 *
 *  Created on: 2011-12-1
 *      Author: Jeff
 */

#include "TResizableFrame.h"
#include <QMargins>
#include <QMouseEvent>
#include <QApplication>
#include <QDesktopWidget>
#include <QCursor>
#include <QVBoxLayout>

TResizableFrame::TResizableFrame(QWidget* parent, Qt::WindowFlags f)
  : QFrame(parent, f), _resizeEnabled(false), _resizeWidth(3),
    _resizeFrame(HT_NONE), _lastHit(HT_NONE)
{
}

void TResizableFrame::setResizeEnabled(bool setting)
{
  if (_resizeEnabled != setting) {
    _resizeEnabled = (setting && !isFixedSize(this));
    if (setting)
      QApplication::instance()->installEventFilter(this);
    else
      QApplication::instance()->removeEventFilter(this);
  }
}

void TResizableFrame::mousePressEvent(QMouseEvent* e)
{
  if (!_resizeEnabled || e->button() != Qt::LeftButton)
    return QFrame::mousePressEvent(e);

  int ht = hitTest(e->pos());
  if (ht == HT_LEFT || ht == HT_TOP || ht == HT_RIGHT || ht == HT_BOTTOM || ht
      == HT_LEFTTOP || ht == HT_RIGHTTOP || ht == HT_LEFTBOTTOM || ht
      == HT_RIGHTBOTTOM) {
    _resizeFrame = ht;
    _resizeStartPosition = e->globalPos();
    emit beginToResize();
    e->accept();
  }
}

void TResizableFrame::mouseReleaseEvent(QMouseEvent* e)
{
  if (_resizeEnabled) {
    if (_resizeFrame != HT_NONE) {
      _resizeFrame = HT_NONE;
      _resizeStartPosition = QPoint();
      emit resizeFinished();
      e->accept();
    }
    int ht = hitTest(e->pos());
    if (_lastHit != ht)
      changeCursor(ht);
  }
}

bool TResizableFrame::eventFilter(QObject* obj, QEvent* e)
{
  if (!_resizeEnabled)
    return QFrame::eventFilter(obj, e);

  if (e->type() == QEvent::MouseMove) {
    QMouseEvent* me = static_cast<QMouseEvent*> (e);
    if (me->buttons() == Qt::NoButton) {
      if (_resizeFrame == HT_NONE) {
        QPoint mpos = me->globalPos();
        if (isAncestorOf(QApplication::widgetAt(mpos))) {
          mpos = mapFromGlobal(mpos);
          int ht = hitTest(mpos);
          if (_lastHit != ht)
            changeCursor(ht);
        } else if (_lastHit != HT_NONE) {
          changeCursor(HT_NONE);
        }
      }
    } else if (me->buttons() == Qt::LeftButton) {
      if (_resizeFrame != HT_NONE)
        resizeFrame();
    }
  } else if (e->type() == QEvent::MouseButtonRelease) {
    if (_resizeFrame != HT_NONE) {
      _resizeFrame = HT_NONE;
      _resizeStartPosition = QPoint();
      changeCursor(HT_NONE);
      emit resizeFinished();
    }
  }
  return false;
}

int TResizableFrame::hitTest(const QPoint& mpos) const
{
  QRect whole(0, 0, frameGeometry().width(), frameGeometry().height());
  QRect test;

  // LeftTop
  test.setRect(0, 0, _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_LEFTTOP;

  // RightTop
  test.setRect(whole.right() - _resizeWidth, 0, _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_RIGHTTOP;

  // LeftBottom
  test.setRect(0, whole.bottom() - _resizeWidth, _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_LEFTBOTTOM;

  // RightBottom
  test.setRect(whole.right() - _resizeWidth, whole.bottom() - _resizeWidth, _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_RIGHTBOTTOM;

  // LeftFrame
  test.setRect(0, _resizeWidth, _resizeWidth, whole.height() - 2 * _resizeWidth);
  if (test.contains(mpos))
    return HT_LEFT;

  // TopFrame
  test.setRect(_resizeWidth, 0, whole.width() - 2 * _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_TOP;

  // RightFrame
  test.setRect(test.right(), _resizeWidth, _resizeWidth, whole.height() - 2 * _resizeWidth);
  if (test.contains(mpos))
    return HT_RIGHT;

  // BottomFrame
  test.setRect(_resizeWidth, test.bottom(), whole.width() - 2 * _resizeWidth, _resizeWidth);
  if (test.contains(mpos))
    return HT_BOTTOM;

  return HT_NONE;
}

void TResizableFrame::changeCursor(int ht)
{
  if (_lastHit == HT_LEFT || _lastHit == HT_RIGHT || _lastHit == HT_TOP
      || _lastHit == HT_BOTTOM || _lastHit == HT_LEFTTOP
      || _lastHit == HT_RIGHTBOTTOM || _lastHit == HT_RIGHTTOP
      || _lastHit == HT_LEFTBOTTOM)
    QApplication::restoreOverrideCursor();

  switch (ht) {
  case HT_LEFT:
  case HT_RIGHT:
    QApplication::setOverrideCursor(Qt::SizeHorCursor);
    break;
  case HT_TOP:
  case HT_BOTTOM:
    QApplication::setOverrideCursor(Qt::SizeVerCursor);
    break;
  case HT_LEFTTOP:
  case HT_RIGHTBOTTOM:
    QApplication::setOverrideCursor(Qt::SizeFDiagCursor);
    break;
  case HT_RIGHTTOP:
  case HT_LEFTBOTTOM:
    QApplication::setOverrideCursor(Qt::SizeBDiagCursor);
    break;
  }

  _lastHit = ht;
}

void TResizableFrame::resizeFrame()
{
  QRect clipRect = getScreenClipRect(this);
  QPoint cpos = QCursor::pos();
  if (cpos.x() < clipRect.left())
    cpos.setX(clipRect.left());
  else if (cpos.x() > clipRect.right())
    cpos.setX(clipRect.right());
  if (cpos.y() < clipRect.top())
    cpos.setY(clipRect.top());
  else if (cpos.y() > clipRect.bottom())
    cpos.setY(clipRect.bottom());

  QPoint offset = cpos - _resizeStartPosition;
  bool toLeft = (offset.x() < 0);
  bool toDown = (offset.y() > 0);
  QRect newRect = frameGeometry();
  QPoint leftLine(0, 0);
  QPoint topLine(0, 0);
  QPoint rightLine(newRect.width(), 0);
  QPoint bottomLine(0, newRect.height());
  switch (_resizeFrame) {
  case HT_LEFT:
    if (toLeft) {
      leftLine = mapToGlobal(leftLine);
      if (leftLine.x() < cpos.x()) {
        _resizeStartPosition = cpos;
        return;
      }
    }
    newRect.setLeft(newRect.left() + offset.x());
    break;
  case HT_TOP:
    if (!toDown) {
      topLine = mapToGlobal(topLine);
      if (topLine.y() < cpos.y()) {
        _resizeStartPosition = cpos;
        return;
      }
    }
    newRect.setTop(newRect.top() + offset.y());
    break;
  case HT_RIGHT:
    if (!toLeft) {
      rightLine = mapToGlobal(rightLine);
      if (rightLine.x() > cpos.x()) {
        _resizeStartPosition = cpos;
        return;
      }
    }
    newRect.setRight(newRect.right() + offset.x());
    break;
  case HT_BOTTOM:
    if (toDown) {
      bottomLine = mapToGlobal(bottomLine);
      if (bottomLine.y() > cpos.y()) {
        _resizeStartPosition = cpos;
        return;
      }
    }
    newRect.setBottom(newRect.bottom() + offset.y());
    break;
  case HT_LEFTTOP:
    if (toLeft) {
      leftLine = mapToGlobal(leftLine);
      if (leftLine.x() < cpos.x())
        offset.setX(0);
    }
    if (!toDown) {
      topLine = mapToGlobal(topLine);
      if (topLine.y() < cpos.y())
        offset.setY(0);
    }
    if (offset.x() == 0 && offset.y() == 0) {
      _resizeStartPosition = cpos;
      return;
    }
    newRect.setLeft(newRect.left() + offset.x());
    newRect.setTop(newRect.top() + offset.y());
    break;
  case HT_RIGHTTOP:
    if (!toLeft) {
      rightLine = mapToGlobal(rightLine);
      if (rightLine.x() > cpos.x())
        offset.setX(0);
    }
    if (!toDown) {
      topLine = mapToGlobal(topLine);
      if (topLine.y() < cpos.y())
        offset.setY(0);
    }
    if (offset.x() == 0 && offset.y() == 0) {
      _resizeStartPosition = cpos;
      return;
    }
    newRect.setRight(newRect.right() + offset.x());
    newRect.setTop(newRect.top() + offset.y());
    break;
  case HT_LEFTBOTTOM:
    if (toLeft) {
      leftLine = mapToGlobal(leftLine);
      if (leftLine.x() < cpos.x())
        offset.setX(0);
    }
    if (toDown) {
      bottomLine = mapToGlobal(bottomLine);
      if (bottomLine.y() > cpos.y())
        offset.setY(0);
    }
    if (offset.x() == 0 && offset.y() == 0) {
      _resizeStartPosition = cpos;
      return;
    }
    newRect.setLeft(newRect.left() + offset.x());
    newRect.setBottom(newRect.bottom() + offset.y());
    break;
  case HT_RIGHTBOTTOM:
    if (!toLeft) {
      rightLine = mapToGlobal(rightLine);
      if (rightLine.x() > cpos.x())
        offset.setX(0);
    }
    if (toDown) {
      bottomLine = mapToGlobal(bottomLine);
      if (bottomLine.y() > cpos.y())
        offset.setY(0);
    }
    if (offset.x() == 0 && offset.y() == 0) {
      _resizeStartPosition = cpos;
      return;
    }
    newRect.setRight(newRect.right() + offset.x());
    newRect.setBottom(newRect.bottom() + offset.y());
    break;
  }
  int left = newRect.left();
  int top = newRect.top();
  int right = newRect.right();
  int bottom = newRect.bottom();
  if (newRect.width() < minimumWidth()) {
    left = frameGeometry().left();
    right = frameGeometry().right();
  }
  if (newRect.height() < minimumHeight()) {
    top = frameGeometry().top();
    bottom = frameGeometry().bottom();
  }
  newRect.setCoords(left, top, right, bottom);
  setGeometry(newRect);
  _resizeStartPosition = cpos;
}

QRect TResizableFrame::getScreenClipRect(QWidget* w)
{
  QRect clipRect;
  QWidget* pw = w->parentWidget();
  if (pw != NULL) {
    QWidget* pa = pw;
    QRect temp = pw->contentsRect();
    while (pw != NULL) {
      clipRect = pw->contentsRect().intersected(temp);
      pa = pw;
      temp = clipRect;
      temp.moveTo(pw->mapToParent(clipRect.topLeft()));
      pw = pw->parentWidget();
    }
    clipRect.moveTo(pa->mapToGlobal(clipRect.topLeft()));
  } else {
    clipRect = QApplication::desktop()->geometry();
  }
  return clipRect;
}

bool TResizableFrame::isFixedSize(QWidget* w)
{
  return ((w->windowFlags() & Qt::MSWindowsFixedSizeDialogHint) || w->minimumSize() == w->maximumSize());
}
